<?php
/**
 * @version 1.1
 * @author jonathan <jo.link.noir@gmail.com>
 * @date 19-06-2010
 */

/**
 */
interface SW_I_Resource extends SW_I_Element_Observer
{
	/**
	 * @param SW_Planet_Storage_Treatment $treatment
	 * @param ArrayIterator $dependency
	 * @param int $asset_group
	 * @param int $race
	 * @return int|null
	 */
	static public function profitPerHour(SW_Planet_Storage_Treatment $treatment, &$dependency, $asset_group, $race);
}

/**
 * Classe parente pour les ressources
 * @exceptions SW_Resource_Exception
 */
abstract class SW_Resource extends SW_Planet_Storage_Treatment implements Iterator, SW_I_Resource
{
	/** @var string */
	const INDEX	= 'resource';

	/** @var SW_DAO_Ressource */
	protected $dao;

	/** @var SW_Dependency|ArrayIterator : {id_asset => {id_type => {id_group, ...}, ...}, ...}*/
	protected $dependency;


	/** @var array : contient les messages d'erreur*/
	public $errorMessages = array(
		0 => 'Erreur ressource',
		1 => 'L\'élément doit être du type de SW_Resource_Element',
		2 => 'Ressource insuffisante',
		3 => 'Ressource inexistante'
	);


	/**
	 * @param SW_Player $player
	 * @param int $idPlanet
	 * @param SW_Dependency $dependency
	 * @param SW_I_DAO_Resource $dao
	 */
	public function __construct(SW_Player $player, $idPlanet, SW_Dependency $dependency, SW_Resource_DAO_Interface $dao)
	{
		parent::__construct($player, $idPlanet, $dependency);
		$this->dao = $dao;
	}

	public function __destruct()
	{}

	/**
	 * Ajoute un élément
	 * @param SW_Element $element
	 * @exception SW_Resource_Exception : code 1
	 */
	public function add(SW_Element $element)
	{
		if (!($element instanceof SW_Resource_Element))
			throw new SW_Resource_Exception($this->errorMessages[1], 1);
		parent::add($element);
	}

	/**
	 * Crée une nouvelle ressource et la sauvegarde
	 * @param int $id_structure
	 * @param int $group
	 * @param int $level
	 * @param int $quantity = 0
	 * @param int $time_end = null ($_SERVER['REQUEST_TIME'])
	 */
	public function createElement($id_structure, $group, $quantity=0, $time_end=null)
	{
		$this->add($this->dao->create(
			$this->idPlanet,
			$id_structure,
			$group,
			$quantity,
			(null === $time_end) ? $_SERVER['REQUEST_TIME'] : $time_end
		));
	}

	/**
	 * Initialise les ressources, les crée si inexistante et attache des observateurs sur les éléments concernés
	 */
	protected function _init()
	{
		//{group => {structure => group_s, ...}, ...}
		$this->dependency->solve($this);
		$this->dependency = $this->dependency->getUnblock();
		$structures = array();
		foreach ($this->dependency as $id_asset => $id_types)
		{
			foreach ($id_types as $id_type => $groups_element)
			{
				if (false === isset($structures[$id_type]))
					$structures[$id_type] = $this->getSiblingInstance($id_type);
				$structure = $structures[$id_type];

				foreach ($groups_element as $group)
				{
					foreach ($structure->getGroup($group) as $element)
					{
						if (false === isset($this->elements[$element->getId()]))
						{
							$this->createElement($element->getId(), $id_asset, 0, $element->getTimeEnd());
						}
					}

					$structure->attachOnGroup($group, $this);
				}
			}
		}

		foreach ($this->elements as $asset)
		{
			$this->initPerHour($asset);
		}
	}

	/**
	 * Update de SplObserver, recalcule les ressources
	 * @param SW_I_Element_Subject $suject : SW_Structure_Element
	 * @attention aucune vérification n'est faite sur la véracité de $subject. Dans une utilisation normale #self serait ajouter dans subject via #self::_init et par conséquant update serait appeler en cas de modification du niveau d'un des éléments d'une ressource.
	 */
	public function update(SW_I_Element_Subject $subject)
	{
		$element = $this->elements[$subject->getId()];
		$element->setTimeEnd($subject->getTimeEnd());
		$this->initPerHour($element);
		$this->dao->update($element);
	}

	/**
	 * @param SW_Resource_Element $resource
	 * @return SW_Resource
	 * @exception SW_Resource_Exception : code 3
	 */
	public function initPerHour(SW_Resource_Element $resource)
	{
		$profit = $this->profitPerHour($this, $this->dependency[$resource->getGroup()], $resource->getGroup(), $this->player->getRace());
		if (null !== $profit)
		{
			$resource->setPerHour($profit);
			return $this;
		}
		else
			throw new SW_Resource_Exception($this->errorMessages[3], 3);
	}

	/*
	 * Retourne les dépendances des ressources. Si les ressources ne sont pas initilisé, retourne un SW_Dependency
	 * @return SW_Dependency|ArrayIterator : {id_asset => {id_type => {id_group, ...}, ...}, ...}
	 */
	/*public function getDependency()
	{
		return $this->dependency;
	}*/

	/**
	 * Vérifie si les ressources passées en paramètre sont inférieures ou égales à celles actuelles
	 * @param array $asset : array(id_asset => valeur, ...)
	 * @param int $timestamp
	 * @return bool
	 */
	final public function sufficient(array $resource, $timestamp)
	{
		foreach ($resource as $group => $v)
		{
			$v = (int)$v;

			if (0 === $v)
				continue;
			if (isset($this->groupElements[$group]) && 0 < $v)
			{
				foreach ($this->groupElements[$group] as $element)
				{
					$quantity = $element->get($timestamp);
					if ($quantity < $v)
						$v -= $quantity;
					else
					{
						$v = 0;
						break;
					}
				}

				if (0 < $v)
					return false;
			}
			else
				return false;
		}
		return true;
	}

	/**
	 * Diminue les ressources. La méthode sufficient est appelé et decrease retourne une exeption SW_Resource_Exception si les ressources sont insuffisantes.
	 * @param array $resources : array(id_asset => valeur, ...)
	 * @param int $timestamp
	 * @exception SW_Resource_Exception : code 2
	 */
	public function decrease(array $resources, $timestamp)
	{
		if (false === $this->sufficient($resources, $timestamp))
			throw new SW_Resource_Exception($this->errorMessages[2], 2);

		$quantities = array();
		foreach ($resources as $group => $v)
		{
			$v = (int)$v;
			if (0 !== $v)
			{
				foreach ($this->groupElements[$group] as $element)
				{
					$quantity = min($element->get($timestamp), $v);
					$v -= $quantity;
					$element->decrease($quantity);
					$element->setTimeEnd($timestamp);
					$quantities[$element->getId()] = $quantity;

					if (0 === $v)
						break;
				}
			}
		}
		$this->dao->decreaseQuantities($quantities);
	}

	/**
	 * Augmente les ressources du 1er élément
	 * @param array $resource
	 * @param int $timestamp
	 * @attention ne vérifie pas si les ressources sont négatives
	 */
	public function increase(array $resource, $timestamp)
	{
		$quantities = array();
		foreach ($resource as $k => $v)
		{
			if (0 !== $v)
			{
				foreach ($this->groupElements[$k] as $element)
				{
					$element->increase($v);
					$element->setTimeEnd($timestamp);
					$quantities[$element->getId()] = $v;
					break;
				}
			}
		}
		$this->dao->increaseQuantities($quantities);
	}

	/**
	 * @param int $timestamp = $_SERVER['REQUEST_TIME']
	 * @param bool $quantityNotNull = false : si true les quantitées zéro ne sont pas retournées
	 * @return array : {group => quantity, ...}
	 */
	public function getQuantities($timestamp = null, $quantityNotNull = false)
	{
		if ($timestamp === null)
			$timestamp = $_SERVER['REQUEST_TIME'];
		$quantities = array();

		foreach ($this->groupElements as $group => $elements)
		{
			$quantities[$group] = 0;
			foreach ($elements as $asset)
				$quantities[$group] += $asset->get($timestamp);
			if ($quantityNotNull && !$quantities[$group])
				unset($quantities[$group]);
		}
		return $quantities;
	}

	/**
	 * @return bool
	 */
	public function valid()
	{
		return null !== key($this->groupElements);
	}

	/**
	 * @code
		next($this->elements_asset)
	 * @endcode
	 */
	public function next()
	{
		next($this->groupElements);
	}

	/**
	 * @code
		reset($this->elements_asset)
	 * @endcode
	 */
	public function rewind()
	{
		reset($this->groupElements);
	}

	/**
	 * Retoune l'id de la ressource itéré
	 * @code
		key($this->elements_asset)
	 * @endcode
	 * @return int
	 */
	public function key()
	{
		return key($this->groupElements);
	}

	/**
	 * Retourne un tableau de d'élément de l'ensemble de toutes les ressources. La quantiter de ressource est calculé avec comme base de temps la date de lancement du script ($_SERVER['REQUEST_TIME'])
	 * @return array : {SW_Ressource_Element, ...}
	 */
	public function current()
	{
		$resources = array();
		$group = key($this->groupElements);
		$per_hour = $quantity = 0;
		foreach ($this->groupElements[$group] as $asset)
		{
			$per_hour += $asset->getPerHour();
			$quantity += $asset->get($_SERVER['REQUEST_TIME']);
		}
		return new SW_Resource_Element(null, $group, $quantity, null, $per_hour);
	}

	/**
	 * @return SW_I_DAO_Structure
	 */
	public function getDao()
	{
		return $this->dao;
	}
}